<?php


/**
 * description...
 * 
 * @author Administrator
 * @category Project
 * @copyright Copyright(c) 2012 
 * @version $Id$
 * 本文件是使用了 pdo 的基本 DB 类 单例模式
 * 依赖性：本文件不依赖其他文件
 *
 *
 * 本类在没查询的时候不会连接数据库
 * 本类的主要功能在于 GO 这个静态方法。
 
 * 主函数 $ret=DnDB::GO($sql,$args) 根据 sql 语句的不同，得到 
 *   多行数组(select)
 *   单行数组(select  limit 1)
 *   单个数值(select columns_x limit 1) 注意：这里必须指定栏目的名字，而且指定 limit 1
 *   修改的行数 (update)  如果返回 0 行则为 TRUE;
 *   插入的 lastinertid (insert) 如果没有，则返回 1
 *   GO 函数支持多个不定参数，根据 pdo 的 无记名绑定模式帮助用户把 ? 变为安全的字符串；
 *   GO 函数的第 2 个参数为 array 的时候会使用 pdo 的记名绑定，警告：没测试和使用过
 *   DnDB::GetQueryedSql 返回的 SQL 可能和实际执行的有些不同， 因为是手工拼接的 。
 *
 * 依赖性
 * 
 * 本文件不依赖其他文件
 * 本文件依赖以下全局配置变量
 * $Dn_DB_Servers;
 *
 * 本数据配置：
 * $config=array(
 *	'user'=>'root',
 *	'password'=>'123456',
 *	
 *	'host'=>'192.168.64.130',
 *	'database'=>'test',
 *	'port'	=>3306,
 *	//'is_log_sql_to_file'=>true,
 *	//'is_log_result'=>false,
 *	//'dsn'=>mysql:host=;port=3306;dbname=
 *	//'log_callback'=>null,
 * );
 *	//TODO 区分一般性 SQL 错误和严重性 SQL 错误， 如 插入碰到单键失败 和 
 **/
class DnDB {
	/** 内部的PDO */
	public $pdo = null;
	/** 专用于 select 的 PDO  TODO */
	//public $pdo_read=null;
	public $is_connected = false;

	public $is_log_lastsql = true;
	public $is_log_sql_to_file = false;
	public $is_log_caller = true;
	public $is_log_result = false; //保存结果集

	public $count = 0;
	public $lastsql = '';

	public $charset = 'utf8';
	public $log_callback = array (
		'DnDebug',
		'Log'
	);
	private $init_config;
	private $last_result;
	/** 单例模式 */
	public static function GetInstance() {
		return self :: Self();
	}
	public static function Self($obj = null) {
		$classname = __CLASS__;
		$mustbe_childclass = false;
		$key = 'Dvaknheo_' . $classname;
		if(is_array($obj)){
			$GLOBALS[$key] = new $classname ($obj);
			return $GLOBALS[$key];
		}
		if ($obj) {
			if ($mustbe_childclass && !is_a($obj, $classname)) {
				$GLOBALS[$key] = $GLOBALS[$key] ? $GLOBALS[$key] : new $classname (array());
			} else {
				$GLOBALS[$key] = $obj;
			}
		} else
			if (!isset ($GLOBALS[$key])) {
				$GLOBALS[$key] = new $classname ($cfg);
			}
		return $GLOBALS[$key];
	}
	/** 构造函数 TODO 减少选项 */
	public function __construct($cfg) {
		
		if (isset ($cfg['is_log_sql_to_file'])) {
			$this->is_log_sql_to_file = $cfg['is_log_sql_to_file'];
		}
		if (isset ($cfg['is_log_result'])) {
			$this->is_log_result = $cfg['is_log_result'];
		}
		if (isset ($cfg['log_callback'])) {
			$this->log_callback = $cfg['log_callback'];
		}
		$this->init_config=$cfg;
		
		
	}
	// TODO 连接多个服务器 
	protected function connect() {
		$cfg=$this->init_config;
		if($cfg){
			$dbuser=$cfg['user'];
			$dbpassword=$cfg['password'];
		}else{
			$dbuser = $GLOBALS['Dn_DB_Servers'][0]['user'];
			$dbpassword = $GLOBALS['Dn_DB_Servers'][0]['password'];
		}
		if(isset($cfg['dsn'])){
			$dsn=$cfg['dsn'];
		}else{
			$dbhost = $cfg['host'];
			$dbname = $cfg['database'];
			$dbport = isset($cfg['port'])?$cfg['port']:3306;
			
			$dsn = 'mysql:host=' . $dbhost .';port=' . $dbport. ';dbname=' . $dbname;
		}
		try {
			$pdo = new PDO($dsn, $dbuser, $dbpassword);
		} catch (Exception $ex) {
			die(__FILE__ . " : " . __LINE__ . " Connect error![$dsn]$dbuser:$dbpassword ".$ex->getMessage());
		}
		
		if('mysql'==substr($dsn,0,strlen('mysql'))){
			$ver = $pdo->getAttribute(PDO :: ATTR_SERVER_VERSION);
			if ($ver > '4.1') {
				$pdo->exec("SET NAMES '" . $this->charset . "'");
			}
			
			//$driver_options = array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf-8' ); // for php bug
			//try {
			//$dbc = new pdo($dsn, $user, $pw, $driver_options);
			if ($pdo > '5.0.1') {
				$pdo->exec("SET sql_mode=''");
			}
		}
		$this->pdo = $pdo;
		$this->is_connected = true;
	}

	public function _go($sql) {
		$args = func_get_args();
		$sql=trim($sql);
		preg_match('/^((select)|(update)|(insert)|(delete)|(replace))(.*?)(limit 1;?)?$/is', $sql, $matches);
		$result=false;
		$sth = $this->getStatment($args);
		if(false){
		}elseif ($matches[2]) {
			if ($matches[8]) {
				//select limit 1
				$result = $sth->fetch(PDO :: FETCH_ASSOC);
				if (1 == sizeof($result) && is_array($result)) {
					// select x from table limit 1
					$result = array_pop(array_values($result));
				}
			} else {
				$result = $sth->fetchAll(PDO :: FETCH_ASSOC);
				
				//单列的时候只要一列数据，意义不大， 双列变成  key, value 形式也会冲突
			}
		} elseif ($matches[3]) {
			$result = $sth->rowCount();
			if(0===$result  && $this->last_result==true){
				$result=true;
			}
		} elseif ($matches[4]) {
			$result = $sth->rowCount();
			if ($result == 1) {
				$lastid = $this->pdo->lastInsertId(); //TODO  貌似这样每次插入一个都要再执行检查 lastinsterid 的 SQL;
				$result = $lastid ? $lastid : 1;
			}
		} elseif ($matches[5]) {
			//delete
			$result = $sth->rowCount();
			if(0===$result  && $this->last_result==true){
				$result=true;
			}
		} elseif ($matches[6]) {
			//replace
			$result = $sth->rowCount();
			if(0===$result  && $this->last_result==true){
				$result=true;
			}
		} else {
			// show ... other
			
			$result = $sth->fetch(PDO :: FETCH_ASSOC);
			
		}
		
		
		if ($this->is_log_result && $this->log_callback) {
			call_user_func($this->log_callback, "result:" . $result);
		}
		
		return $result;
	}
	protected function getStatment($args) {
		$sql = array_shift($args);
		$ext = $args;
		if (1 == sizeof($args) && is_array($args[0])) {
			// 这里是  key=>value 的模式。 // TODO 完成
			$ext = $args[0];
		} else {
			$ext = $args;
		}

		if (!$this->is_connected) {
			$this->connect();
		}

		$this->pdo->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
		try{
		
		
		$sth = $this->pdo->prepare($sql);
		
		$time1 = microtime(true);

		
			$this->last_result=$sth->execute($ext);
		}catch(PDOException  $ex){
			echo "<pre>\n";
			var_dump($sql);
			var_dump($args);
			
			print $ex->getMessage();
			debug_print_backtrace();
			echo "</pre>\n";
		}
		$time2 = microtime(true);
		$code = $sth->errorCode();
		$this->log($sql, $args, $time2 - $time1, $code); //log 放后面知道执行本语句大致花了多长时间

		$this->count++;
		//$sth->setFetchMode(PDO :: FETCH_OBJ);
		return $sth;
	}
	protected function log($sql, $ext, $querytime, $code) {
		if (!$this->is_log_lastsql && !$this->is_log_sql_to_file) {
			return;
		}

		$sql = $this->_getSql($sql, $ext);
		if ($this->is_log_lastsql) {
			$this->lastsql = $sql;
		}

		if ($this->is_log_sql_to_file) {

			$backtrace = debug_backtrace();
			$x=$backtrace;
			if(!isset($x['file'])){
				@$x=$x[5];
			}
			@$str = basename($x['file']) . ":" . $x['line'] . "\n";

			$str .= "--------------SQL Time Cost:" . $querytime . "\n";
			if ($querytime >= 0.5) {
				$str = str_replace('-', '~', $str);
			}
			if ($code) {
				$str = "[$code]".str_replace('-', '!', $str); 
			}
			$str .= $sql;
			if ($this->log_callback) {
				call_user_func($this->log_callback, $str);
			}
		}
	}
	public function _getSql($sql, $args) {
		if (!$args) {
			return $sql;
		}
		if (1 == sizeof($args) && is_array($args[0])) {
			$args = $args[0];

			return "TODO" . __FILE__ . ":" . __LINE__;
		}

		//这里把分离的 arg 组合起来
		$l = sizeof($args);
		for ($i = 0; $i < $l; $i++) {
			$args[$i] = $this->quote($args[$i]);
		}
		return $this->joinstr($sql, $args);

	}
	private function joinstr($str, $array) {
		$l_array = sizeof($array);

		$str_array = explode("?", $str);
		$l = sizeof($str_array);

		$l_str = sizeof($str_array) - 1;
		$ret = '';
		for ($i = 0; $i < $l; $i++) {
			$ret .= $str_array[$i];
			if ($i == $l_str) {
				break;
			}
			$ret .= ($i < $l_array) ? $array[$i] : '?';
		}
		return $ret;
	}
	public function quote($str) {
		if (!$this->is_connected) {
			$this->connect();
		}
		return $this->pdo->quote($str);
	}
	///////////////////////////////////////////////////////////////////////////
	/**
	 * 这个静态方法是这个类的重点。
	 */
	public static function GO($sql) {
		$method = '_go';
		$me = self :: GetInstance();
		$args = func_get_args();

		return call_user_func_array(array (
			& $me,
			$method
		), $args);
	}
	public static function Last()
	{
		$me = self :: GetInstance();
		return $me->lastsql;
	}
	/**
	 * 和 GO 不同的是，这里只得到 SQL ，不查询
	 */
	public static function GetSql($sql) //As GO
	{
		$method = '_getsql';
		$me = self :: GetInstance();
		$args = func_get_args();

		return call_user_func_array(array (
			& $me,
			$method
		), $args);

	}
	/**
	 * 把列表转换成安全的 sql in 里面的字符
	 * 然后  "select * from tablename where id in ($in)";
	 */
	public static function A2In($a) {
		if (!$a)
			return 'NULL';
		$str = '';
		foreach ($a as $el) {
			$str .= self :: E($el) . ',';
		}
		$str = substr($str, 0, -1);
		return $str;
	}
	/**
	 * PDO 编码 把字符串编码成安全的 sql
	 * 等价于 "'".mysql_real_escape_string()."'"
	 */
	public static function E($str) {
		$me = self :: GetInstance();
		return $me->quote($str);
	}
	public static function _E(&$str)
	{
		$me = self :: GetInstance();
		$str=$me->quote($str);
		return $str;
	}
	public static function A2U($a)
	{
		
		$flag=array_walk($a,array(__CLASS__,'_E'));
		foreach($a as $k => $v){
			$o[]="`".$k."`=".$v;
		}
		return implode(',',$o);
	}
	public static function A2I($a)
	{
		array_walk($a,array(__CLASS__,'_E'));
		$keys=array();
		foreach($a as $k => $v){
			$keys[]="`".$k."`";
		}
		return "(".implode(',',$keys).")VALUES(".implode(',',array_values($a)).")";
	}
}